/*
 * This file is part of the xOperator SPARQL/XMPP agent.
 * For further information see: http://xoperator.aksw.org
 * Copyright (C) 2007-2008  Jörg Unbehauen

 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.aksw.xoperator;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.aksw.xoperator.aiml.AimlFacade;
import org.aksw.xoperator.command.Command;
import org.aksw.xoperator.security.IAccessControl;
import org.aksw.xoperator.sparql.ISparqlEndpoint;
import org.aksw.xoperator.sparql.SparqlEndpointFacade;
import org.aksw.xoperator.sparql.SparqlEndpointIdentifier;
import org.aksw.xoperator.sparql.SparqlQuery;
import org.aksw.xoperator.sparql.SparqlResult;
import org.aksw.xoperator.sparql.p2p.SPARQLQueryOverXmpp;
import org.aksw.xoperator.xmpp.JabberClientManager;
import org.aksw.xoperator.xmpp.JabberUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;
import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManagerListener;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;


/**
 * Manages the interaction beween the ui and the other components.
 *
 * @author joerg
 */
public class Controller implements MessageListener, ChatManagerListener,
    PacketListener {
    private static Log log = LogFactory.getLog(Controller.class);
    private static Log conversationLog = LogFactory.getLog("conversation");
    private static Log p2pLog = LogFactory.getLog("p2p");
    private AimlFacade aimlFacade;
    private Collection<Command> commands;
    private JabberClientManager jcmanager;
    private ISparqlEndpoint endpointFacade;
    private IAccessControl accessControl;
    

    public Controller(List<Command> commands, AimlFacade aimlFacade,
        JabberClientManager jcmanager, SparqlEndpointFacade facade, IAccessControl accessControl) {
        super();
        this.commands = commands;
        this.aimlFacade = aimlFacade;
        this.jcmanager = jcmanager;
        this.endpointFacade = facade;
        this.accessControl = accessControl;
        jcmanager.addChatManagerListener(this);
        jcmanager.registerP2PListener(new SPARQLQueryOverXmpp.IQPaketListener(
                this, jcmanager));
        //sort the commands array alphabetically
        Collections.sort(commands,
            new Comparator<Command>() {
                public int compare(Command cmd1, Command cmd2) {
                    return cmd1.getKeyWord().length() -
                    cmd2.getKeyWord().length();
                }
            });
    }

    /**
     * Processes a Message received through the P2P facilities. Defines that only the built in store should be asked.
     *
     * @param query the incoming query
     *
     * @return the SPARQL Result, serialized as a String
     */
    public String processIncomingSparqlQuery(SPARQLQueryOverXmpp incomingQuery) {
    	SparqlQuery query = incomingQuery.getSparqlQuery();
        p2pLog.info("received " + query.getQueryString());
        query.setAgentsToAsk(new ArrayList<SparqlEndpointIdentifier>());
        query.setStoresToAsk(new ArrayList<SparqlEndpointIdentifier>());
        query.setAskLocal(true);
        endpointFacade.process(query);

        List<String> results = new ArrayList<String>();

        for (SparqlResult sresult : query.getResults().values()) {
            results.add(sresult.getAnswer());
        }

        String result = uglySparqlResultCombiner(results);
        p2pLog.info("answered: " + StringUtils.substring(result, 0, 200));

        return result;
    }

    /**
     * Processes an incoming chat message.
     *
     * @param chat DOCUMENT ME!
     * @param message DOCUMENT ME!
     */
    public void processMessage(Chat chat, Message message) {
    	
    	//check if message is allowed to be further processed
    	if(accessControl.allow(message)){
    		
    	
    	
        if (message.getBody() != null) {
            String composingId = RandomStringUtils.random(6);

            try {
                chat.sendMessage(JabberUtils.composingNotification(
                        composingId));

                String messBody = message.getBody();
                conversationLog.info("Received: " + messBody);

                boolean isCmd = false;

                for (Command command : commands) {
                    if (messBody.startsWith(command.getKeyWord())) {
                        command.perform(chat, message);
                        isCmd = true;

                        break;
                    }
                }

                if (isCmd == false) {
                    aimlFacade.letTheBotRespond(chat, message);
                }

                chat.sendMessage(JabberUtils.composingCancelNotification(
                        composingId));
            } catch (Exception e) {
                log.error("Error while processing content ", e);
            }
        }
    	}else{
    		try {
				chat.sendMessage(accessControl.getDenialResponse(message));
			} catch (XMPPException e) {
				log.error("Error sending the access denied message: ", e);
			}
    	}
    }

    /**
     * Adds itself to the as a chatManager, required for chat control.
     *
     * @param chat DOCUMENT ME!
     * @param arg1 DOCUMENT ME!
     */
    public void chatCreated(Chat chat, boolean arg1) {
        chat.addMessageListener(this);
    }

    /**
     * Not yet implemented.
     *
     * @param packet DOCUMENT ME!
     */
    public void processPacket(Packet packet) {
    }

    /**
     * Merges SPARQL-Result sets.
     *
     * @param results the List of String-serialized SPARQL-Documents to be
     *        merged together.
     *
     * @return The XML-Document as one single String
     */
    public static String uglySparqlResultCombiner(List<String> results) {
        StringBuffer resultBuffer = new StringBuffer();
        SAXBuilder builder = new SAXBuilder();
        Document doc = null;
        XPath xpath;

        try {
            xpath = XPath.newInstance("/res:sparql/res:results");

            xpath.addNamespace(Constants.HTTP_WWW_W3_ORG_2005_SPARQL_RESULTS);

            for (String result : results) {
                Document scrapeDoc = builder.build(new StringReader(result));

                // check if there is content in it
                Object resResults = xpath.selectSingleNode(scrapeDoc);

                if (resResults != null) {
                    if (doc == null) {
                        doc = scrapeDoc;
                    } else {
                        List resultList = ((Element) resResults).getChildren();
                        List<Element> safeList = new ArrayList<Element>();

                        for (Object object : resultList) {
                            safeList.add((Element) object);
                        }

                        for (Element element : safeList) {
                            doc.getRootElement()
                               .getChild("results",
                                Constants.HTTP_WWW_W3_ORG_2005_SPARQL_RESULTS)
                               .addContent(element.detach());
                        }
                    }
                }
            }
        } catch (RuntimeException e) {
            log.error("Error while merging the results: ", e);
        } catch (JDOMException e) {
            log.error("Error in the xml structure: ", e);
        } catch (IOException e) {
            log.error("Error reading the input:", e);
        }

        if (doc != null) {
            XMLOutputter out = new XMLOutputter();
            resultBuffer.append(out.outputString(doc));
        }

        return resultBuffer.toString();
    }
    
}
